#include "Nodes.h"

//define send / receive tags
#define SEND_WORK 1
#define RECEIVE_WORK SEND_WORK

#define SEND_SIZE 2
#define RECEIVE_SIZE SEND_SIZE 

/* usage */
void usage(){
	std::cout << std::endl;
	std::cout << "###############################################################" << std::endl;
	std::cout << "### Syntax Error: 					  ###" << std::endl;
	std::cout << "### ./nbody <number of bodys> <simulation steps> <dt [opt]> ###" << std::endl;
	std::cout << "###############################################################" << std::endl;
	return;
}

/* distributeWork */
void distributeWork(int nodes,int bodys,int *workload){
	int workPerNode = bodys / nodes;
	int nbodys = bodys;

	std::cout << "#Work per Node: " << workPerNode << std::endl;

	for(int i = 0; i < nodes; i++){
		if(nbodys - workPerNode >= 0){
			nbodys -= workPerNode;
			workload[i] = workPerNode;
		}else{
			if(nbodys > 0){
				workload[i] = nbodys;
				nbodys = 0;
			}else{
				workload[i] = 0;
			}
		}
		std::cout << "# " << workload[i] << " Bodys " << " for Node " << i << ". Bodys left " << nbodys << std::endl;
	}

	if(nbodys != 0 && nodes > 0){
		int index = nodes - 1;
		workload[index] = workPerNode + nbodys;
		std::cout << "# NEW workload for node " << index << " : " << workload[index] << std::endl; 
	}

	return;
}

/* calcRuntime() */
unsigned long calcRuntime(timeVal start, timeVal end)
{
	/* calc time difference */
	timeVal diff;
	diff.tv_sec = end.tv_sec - start.tv_sec;
	if (start.tv_usec > end.tv_usec){
		diff.tv_usec = (1e6 - end.tv_usec) + start.tv_usec;
	}else{
		diff.tv_usec = end.tv_usec - start.tv_usec;
	}

	return diff.tv_sec * 1e6 + diff.tv_usec;
}

/* doWork */
unsigned long doWork(int rank,int size,int steps,int work,int bodys,Body *nBodys){
	int prevNeighbour;
	int nextNeighbour;
	int prevWork = 0;
	int nextWork = 0;
	unsigned int ownBaseId = 0;
	unsigned long endless = 0;
	const int count = 2;

	Particle* newBodys;
	MPI_Request request0,request1;
	MPI_Status status;
	unsigned long runtime = 0;
	timeVal start,end;

	/* set up datatype particles*/
	MPI_Datatype particles,oldtype[count];
	int blocks[count];

	MPI_Aint offsets[count], extent;

	offsets[0] = 0;
	oldtype[0] = MPI_UNSIGNED;
	blocks[0] = 1; //1 u_int in struct Particle

	MPI_Type_extent(MPI_UNSIGNED,&extent);

	offsets[1] = 1 * extent;
	oldtype[1] = MPI_DOUBLE;
	blocks[1] = 5; // 4 double in struct Particle

	MPI_Type_struct(count, blocks, offsets, oldtype, &particles); 
	MPI_Type_commit(&particles);

	/* determine previous and next neighbour */
	if(rank == 0){
		prevNeighbour = size - 1;
		nextNeighbour = 1;
	}else if(rank == (size -1)){
		prevNeighbour = size - 2;
		nextNeighbour = 0;
	}else{
		prevNeighbour = rank - 1;
		nextNeighbour = rank + 1;
	}

	std::cout << "# [Rank] " << rank << " [prev.Node] " << prevNeighbour << " [nextNode] " << nextNeighbour << std::endl;

	if(rank == 0){
		gettimeofday(&start,NULL);
	}
	Particle *ownParticles = NULL;
	//Particle* ownParticles = new Particle[work];

	ownBaseId = nBodys[0].getId();
	for(int i = 0; i < steps; i++){
		if(ownParticles != NULL){
			delete [] ownParticles;
		}
		
		ownParticles = new Particle[work];

		for(int j = 0; j < work; j++){
			ownParticles[j] = nBodys[j].getParticle();
			//print actual state
#if RUNTIME_MEASUREMENT == 0
			std::cout << "[Step] " << i << " out_of " << steps 
				  << nBodys[j]
				  << " #Rank " << rank << std::endl;
#endif
		}

		nextWork = work;

		//MPI_Barrier(MPI_COMM_WORLD);

		while(1){
			//send work to the next node
			MPI_Isend(&nextWork,1,MPI_INT,nextNeighbour,SEND_SIZE,MPI_COMM_WORLD,&request0);
			MPI_Isend(ownParticles,nextWork,particles,nextNeighbour,SEND_WORK,MPI_COMM_WORLD,&request1);

			//receive work from previous node
			MPI_Recv(&prevWork,1,MPI_INT,prevNeighbour,RECEIVE_SIZE,MPI_COMM_WORLD,&status);
			newBodys = new Particle[prevWork];
			MPI_Recv(newBodys,prevWork,particles,prevNeighbour,RECEIVE_WORK,MPI_COMM_WORLD,&status);
	
#if DEBUG == 1
		std::cout << "# Rank " << rank << " Step " << i << " received amount of bodies " << prevWork << " id of first body " << newBodys[0].id << std::endl;
		std::cout << "# Rank " << rank << " Step " << i << " received body pos  [" << newBodys[0].posx << "][" << newBodys[0].posy << "][" <<newBodys[0].posz << "] Mass: "<< newBodys[0].mass << std::endl;
#endif
			//calculations
			for(int k = 0; k < work; k++){
				nBodys[k].calcAcceleration(newBodys,prevWork);
			}

			if(newBodys[0].id == ownBaseId){
#if DEBUG == 1
				std::cout << "# Rank " << rank << " step successfully executed" << std::endl;
#endif
				delete [] newBodys;
				break;
			}

			/* prepare send buffer for the next iteration */
			nextWork = prevWork;

			if(work != prevWork){
				delete [] ownParticles;
				ownParticles = new Particle[prevWork];
			}

			for(int k = 0; k < prevWork; k++){
				ownParticles[k] = newBodys[k];
			}

			delete [] newBodys;
			endless++;
			if(endless == (ULONG_MAX)){
				std::cout << "# Rank " << rank << " abort execution. Endless overflow." << std::endl;
				std::cout << std::endl;
				break;
			}

			//MPI_Barrier(MPI_COMM_WORLD);
		}

#if DEBUG == 1
				std::cout << "# Rank " << rank << " Update positions " << std::endl;
#endif		
		for(int h = 0; h < work; h++){
			nBodys[h].updatePosition();
		}

		MPI_Barrier(MPI_COMM_WORLD); //synchronize before next step
	}

	if(rank == 0){
		gettimeofday(&end,NULL);
		runtime = calcRuntime(start,end);
	}

	MPI_Barrier(MPI_COMM_WORLD); //synchronize before free datatype
	MPI_Type_free(&particles);

	return runtime;
}

unsigned long doWorkOnSingleNode(int steps,int size,Body *nBodys){
	timeVal start,end;
	unsigned long runtime = 0;
	gettimeofday(&start,NULL);

	for(int k = 0; k < steps; k++){

		for(int j = 0; j < size; j++){
		//print actual state
			nBodys[j].updatePosition();
#if RUNTIME_MEASUREMENT == 0
			std::cout << "[Step]" << k << " out_of " << steps		
			<< nBodys[j]
			<< " #Single-Node " << std::endl;
#endif
		}	
	
		for(int i = 0; i < size; i++){
			//body-body-interactions
			nBodys[i].calcAcceleration(nBodys,size);
		}	
		
	}

	gettimeofday(&end,NULL);
	runtime = calcRuntime(start,end);
	return runtime;
}

